//
// Created by yonghu0 on 25-10-31.
//

#include "Judger.h"
#include <cstring>
#include <fstream>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <bits/fs_fwd.h>
#include <iostream>

extern "C"
{
#include "runner.h"
}

size_t GetFileSize(const char *fileName)
{
    if (fileName == nullptr)
    {
        return 0;
    }
    struct stat statbuf;

    stat(fileName, &statbuf);

    size_t filesize = statbuf.st_size;

    return filesize;
}

Judger::Judger()
{
}

Json::Value Judger::Run(Json::Value &runjson)
{
    if (!Init(runjson))
        Done();

    if (m_language == "C")
    {
        if (!CompileC())
            return Done();

        if (!RunProgramC_Cpp())
            return Done();
    }
    else if (m_language == "C++")
    {
        if (!CompileCpp())
            Done();

        if (!RunProgramC_Cpp())
            Done();
    }
    else if (m_language == "Go")
    {
        if (!CompileGo())
            Done();

        if (!RunProgramGo())
            Done();
    }
    else if (m_language == "Java")
    {
        if (!CompileJava())
            return Done();

        if (!RunProgramJava())
            return Done();
    }
    else if (m_language == "Python2")
    {
        if (!CompilePython2())
            return Done();

        if (!RunProgramPython2())
            return Done();
    }
    else if (m_language == "Python3")
    {
        if (!CompilePython3())
            return Done();

        if (!RunProgramPython3())
            return Done();
    }
    else if (m_language == "JavaScript")
    {
        if (!CompileJavaScript())
            return Done();

        if (!RunProgramJavaScript())
            return Done();
    }

    return Done();
}

bool Judger::Init(Json::Value &initjson)
{
    m_submitid = initjson["SubmitId"].asString();
    m_problemid = initjson["ProblemId"].asString();
    m_judgenum = stoi(initjson["JudgeNum"].asString());
    m_code = initjson["Code"].asString();
    m_language = initjson["Language"].asString();
    m_timelimit = stoi(initjson["TimeLimit"].asString());
    m_maxtimelimit = m_timelimit * 2;
    m_memorylimit = stoi(initjson["MemoryLimit"].asString()) * 1024 * 1024;
    m_maxmemorylimit = m_memorylimit * 2;

    m_runpath = "./" + m_submitid + "/";
    m_datapath = "../../problemdata/" + m_problemid + "/";
    m_isspj = false;

    m_resjson.clear();
    m_result = PJ;
    m_reason.clear();
    m_length.clear();
    m_runtime = 0;
    m_runmemory = 0;

    //创建提交目录
    m_command = "mkdir " + m_submitid;
    if (system(m_command.c_str()) == -1)
    {
        m_result = SE;
        return false;
    }

    //将提交代码写入文件
    if (m_language == "C")
    {
        m_command = m_runpath + "main.c";
    }
    else if (m_language == "C++")
    {
        m_command = m_runpath + "main.cpp";
    }
    else if (m_language == "Go")
    {
        m_command = m_runpath + "main.go";
    }
    else if (m_language == "Java")
    {
        m_command = m_runpath + "Main.java";
    }
    else if (m_language == "Python2")
    {
        m_command = m_runpath + "main.py";
    }
    else if (m_language == "Python3")
    {
        m_command = m_runpath + "main.py";
    }
    else if (m_language == "JavaScript")
    {
        m_command = m_runpath + "main.js";
    }
    else
    {
        m_result = SE;
        return false;
    }
    std::ofstream codefile(m_command.c_str());
    if (!codefile.is_open())
    {
        m_result = SE;
        return false;
    }
    codefile << m_code;
    codefile.close();

    //计算文件大小
    m_length = std::to_string(GetFileSize(m_command.data())) + "B";

    //编译SPJ
    CompileSPJ();

    return true;
}

bool Judger::CompileC()
{
    m_command = "timeout 10 gcc " + m_runpath + "main.c -fmax-errors=3 -o " + m_runpath + "main -O2 -std=c++11 2>" +
                m_runpath + "compileinfo.txt";
    if (system(m_command.data()) == -1)
    {
        m_result = SE;
        return false;
    }

    //如果编译失败，返回错误信息
    m_command = m_runpath + "main";
    if (access(m_command.data(),F_OK) != 0)
    {
        GetCompilationFailed();
        return false;
    }

    return true;
}

bool Judger::CompileCpp()
{
    //进行c++编译
    m_command = "timeout 10 g++ " + m_runpath + "main.cpp -fmax-errors=3 -o " + m_runpath + "main -O2 -std=c++11 2>" +
                m_runpath + "compileinfo.txt";
    if (system(m_command.data()) == -1)
    {
        m_result = SE;
        return false;
    }

    //如果编译失败，返回错误信息
    m_command = m_runpath + "main";
    if (access(m_command.data(),F_OK) != 0)
    {
        GetCompilationFailed();
        return false;
    }

    return true;
}

bool Judger::CompileGo()
{
    m_command = "go build -o " + m_runpath + "main " + m_runpath + "main.go 2>" + m_runpath + "compileinfo.txt";
    if (system(m_command.data()) == -1)
    {
        m_result = SE;
        return false;
    }

    //如果编译失败，返回错误信息
    m_command = m_runpath + "main";
    if (access(m_command.data(),F_OK) != 0)
    {
        GetCompilationFailed();
        return false;
    }

    return true;
}

bool Judger::CompilePython2()
{
    // 进行Python2编译
    m_command = "python2 -m py_compile " + m_runpath + "main.py 2>" + m_runpath + "compileinfo.txt";
    if (system(m_command.data()) == -1)
    {
        m_result = SE;
        return false;
    }

    m_command = m_runpath + "main.pyc";
    // 编译失败
    if (access(m_command.data(), F_OK) == -1)
    {
        // 返回编译失败原因
        GetCompilationFailed();
        return false;
    }
    return true;
}

bool Judger::CompilePython3()
{
    // 进行Python3编译
    m_command = "python3 -m py_compile " + m_runpath + "main.py 2>" + m_runpath + "compileinfo.txt";
    if (system(m_command.data()) == -1)
    {
        m_result = SE;
        return false;
    }

    m_command = m_runpath + "__pycache__/main.cpython-38.pyc";
    // 编译失败
    if (access(m_command.data(), F_OK) == -1)
    {
        // 返回编译失败原因
        GetCompilationFailed();
        return false;
    }
    return true;
}

bool Judger::CompileJava()
{
    // 进行java编译
    m_command = "javac " + m_runpath + "Main.java -d " + m_runpath + "Main 2>" + m_runpath + "compileinfo.txt";
    if (system(m_command.data()) == -1)
    {
        m_result = SE;
        return false;
    }

    m_command = m_runpath + "Main";
    // 编译失败
    if (access(m_command.data(), F_OK) == -1)
    {
        // 返回编译失败原因
        GetCompilationFailed();
        return false;
    }
    return true;
}

bool Judger::CompileJavaScript()
{
    m_command = "/usr/bin/nodejs --check " + m_runpath + "main.js 2>" + m_runpath + "compileinfo.txt";
    if (system(m_command.data()) == -1)
    {
        m_result = SE;
        return false;
    }

    m_command = m_runpath + "main.js";
    // 编译失败
    if (access(m_command.data(), F_OK) == -1)
    {
        // 返回编译失败原因
        GetCompilationFailed();
        return false;
    }
    return true;
}

bool Judger::RunProgramC_Cpp()
{
    struct config conf;
    conf.max_cpu_time = m_timelimit;
    conf.max_real_time = m_maxtimelimit;
    conf.max_memory = m_maxmemorylimit;
    conf.max_stack = 32 * 1024 * 1024;
    conf.max_process_number = 200;
    conf.max_output_size = -1;
    std::string exec_path = m_runpath + "main";
    conf.exe_path = exec_path.data();
    std::string error_path = m_runpath + "error.out";
    conf.error_path = error_path.data();
    std::string log_path = m_runpath + "judger.log";
    conf.log_path = log_path.data();
    conf.seccomp_rule_name = (char *) "c_cpp";
    conf.uid = 0;
    conf.gid = 0;
    memset(&conf.args, 0, sizeof(conf.args));
    memset(&conf.env, 0, sizeof(conf.env));

    RunProgram(conf);
    return true;
}

bool Judger::RunProgramGo()
{
    struct config conf;
    conf.max_cpu_time = m_timelimit;
    conf.max_real_time = m_maxtimelimit;
    conf.max_memory = m_maxmemorylimit;
    conf.max_stack = 32 * 1024 * 1024;
    conf.max_process_number = 200;
    conf.max_output_size = -1;
    std::string exec_path = m_runpath + "main";
    conf.exe_path = exec_path.data();
    std::string error_path = m_runpath + "error.out";
    conf.error_path = error_path.data();
    std::string log_path = m_runpath + "judger.log";
    conf.log_path = log_path.data();
    conf.seccomp_rule_name = (char *) "golang";
    conf.uid = 0;
    conf.gid = 0;
    memset(&conf.args, 0, sizeof(conf.args));
    memset(&conf.env, 0, sizeof(conf.env));
    conf.env[0] = (char *) "LANG=en_US.UTF-8";
    conf.env[1] = (char *) "LANGUAGE=en_US:en";
    conf.env[2] = (char *) "LC_ALL=en_US.UTF-8";

    RunProgram(conf);
    return true;
}

bool Judger::RunProgramPython2()
{
    // 创建结构体
    struct config conf;

    conf.max_cpu_time = m_timelimit * 2;
    conf.max_real_time = m_maxtimelimit * 2;
    conf.max_memory = m_maxmemorylimit * 2;

    conf.max_process_number = 200;
    conf.max_output_size = -1;
    conf.max_stack = 32 * 1024 * 1024;
    std::string exe_path = "/usr/bin/python2";
    conf.exe_path = (char *) exe_path.data();
    std::string error_path = m_runpath + "error.out";
    conf.error_path = (char *) error_path.data();
    std::string log_path = m_runpath + "judger.log";
    conf.log_path = (char *) log_path.data();
    conf.seccomp_rule_name = (char *) "general";
    conf.uid = 0;
    conf.gid = 0;
    memset(conf.args, 0, sizeof(conf.args));
    memset(conf.env, 0, sizeof(conf.env));

    std::string exe_file = m_runpath + "main.pyc";
    conf.args[0] = (char *) "/usr/bin/python2";
    conf.args[1] = (char *) exe_file.data();

    conf.env[0] = (char *) "LANG=en_US.UTF-8";
    conf.env[1] = (char *) "LANGUAGE=en_US:en";
    conf.env[2] = (char *) "LC_ALL=en_US.UTF-8";

    RunProgram(conf);
    return true;
}

bool Judger::RunProgramPython3()
{
    // 创建结构体
    struct config conf;

    conf.max_cpu_time = m_timelimit * 2;
    conf.max_real_time = m_maxtimelimit * 2;
    conf.max_memory = m_maxmemorylimit * 2;

    conf.max_process_number = 200;
    conf.max_output_size = -1;
    conf.max_stack = 32 * 1024 * 1024;
    std::string exe_path = "/usr/bin/python3";
    conf.exe_path = (char *) exe_path.data();
    std::string error_path = m_runpath + "error.out";
    conf.error_path = (char *) error_path.data();
    std::string log_path = m_runpath + "judger.log";
    conf.log_path = (char *) log_path.data();
    conf.seccomp_rule_name = (char *) "general";
    conf.uid = 0;
    conf.gid = 0;
    memset(conf.args, 0, sizeof(conf.args));
    memset(conf.env, 0, sizeof(conf.env));

    std::string exe_file = m_runpath + "__pycache__/main.cpython-38.pyc";
    conf.args[0] = (char *) "/usr/bin/python3";
    conf.args[1] = (char *) exe_file.data();

    conf.env[0] = (char *) "LANG=en_US.UTF-8";
    conf.env[1] = (char *) "LANGUAGE=en_US:en";
    conf.env[2] = (char *) "LC_ALL=en_US.UTF-8";
    conf.env[3] = (char *) "PYTHONIOENCODING=utf-8";

    RunProgram(conf);
    return true;
}

bool Judger::RunProgramJava()
{
    struct config conf;
    m_memorylimit = m_memorylimit * 3;

    conf.max_cpu_time = m_timelimit * 3;
    conf.max_real_time = m_maxtimelimit * 3;
    conf.max_memory = -1;

    conf.max_process_number = 200;
    conf.max_output_size = -1;
    conf.max_stack = 32 * 1024 * 1024;
    std::string exe_path = "/usr/bin/java";
    conf.exe_path = (char *) exe_path.data();
    std::string error_path = m_runpath + "error.out";
    conf.error_path = (char *) error_path.data();
    std::string log_path = m_runpath + "judger.log";
    conf.log_path = (char *) log_path.data();
    conf.seccomp_rule_name = nullptr;
    conf.memory_limit_check_only = 1;
    conf.uid = 0;
    conf.gid = 0;
    memset(conf.args, 0, sizeof(conf.args));
    memset(conf.env, 0, sizeof(conf.env));

    std::string exe_file = m_runpath + "Main";
    std::string tmp_maxmemory = "-XX:MaxRAM=" + std::to_string(m_memorylimit / 1024) + "k";
    conf.args[0] = (char *) "/usr/bin/java";
    conf.args[1] = (char *) "-cp";
    conf.args[2] = (char *) exe_file.data();
    conf.args[3] = (char *) tmp_maxmemory.data();
    conf.args[4] = (char *) "-Djava.security.policy==policy";
    conf.args[5] = (char *) "-Djava.awt.headless=true";
    conf.args[6] = (char *) "Main";

    conf.env[0] = (char *) "LANG=en_US.UTF-8";
    conf.env[1] = (char *) "LANGUAGE=en_US:en";
    conf.env[2] = (char *) "LC_ALL=en_US.UTF-8";

    RunProgram(conf);
    return true;
}

bool Judger::RunProgramJavaScript()
{
    // 创建结构体
    struct config conf;

    conf.max_cpu_time = m_timelimit * 2;
    conf.max_real_time = m_maxtimelimit * 2;
    conf.max_memory = m_maxmemorylimit * 2;

    conf.max_process_number = 200;
    conf.max_output_size = -1;
    conf.max_stack = 32 * 1024 * 1024;
    std::string exe_path = "/usr/bin/nodejs";
    conf.exe_path = (char *) exe_path.data();
    std::string error_path = m_runpath + "error.out";
    conf.error_path = (char *) error_path.data();
    std::string log_path = m_runpath + "judger.log";
    conf.log_path = (char *) log_path.data();
    conf.seccomp_rule_name = (char *) "node";
    conf.memory_limit_check_only = 1;
    conf.uid = 0;
    conf.gid = 0;
    memset(conf.args, 0, sizeof(conf.args));
    memset(conf.env, 0, sizeof(conf.env));

    std::string exe_file = m_runpath + "main.js";
    conf.args[0] = (char *) "/usr/bin/nodejs";
    conf.args[1] = (char *) exe_file.data();

    conf.env[0] = (char *) "LANG=en_US.UTF-8";
    conf.env[1] = (char *) "LANGUAGE=en_US:en";
    conf.env[2] = (char *) "LC_ALL=en_US.UTF-8";

    RunProgram(conf);
    return true;
}


Json::Value Judger::Done()
{
    if (m_result == PJ)
        m_result = AC;

    m_resjson["SubmitId"] = m_submitid;
    m_resjson["Status"] = m_result;
    m_resjson["RunTime"] = std::to_string(m_runtime) + "MS";
    m_resjson["RunMemory"] = std::to_string(int(m_runmemory / 1024 / 1024)) + "MB";
    m_resjson["Length"] = m_length;
    m_resjson["ComplierInfo"] = m_reason;

    m_command = "rm -rf " + m_runpath;
    system(m_command.c_str());

    std::cout << m_resjson.toStyledString() << std::endl;

    return m_resjson;
}

bool Judger::GetCompilationFailed()
{
    //将编译错误信息从文件中读取
    m_command = m_runpath + "compileinfo.txt";
    std::ifstream compilefile(m_command.c_str());
    if (!compilefile.is_open())
    {
        m_result = SE;
        return false;
    }
    std::string reason{
        std::istreambuf_iterator<char>(compilefile),
        std::istreambuf_iterator<char>()
    };
    m_result = CE;
    m_reason = reason;

    return true;
}

bool Judger::RunProgram(struct config &conf)
{
    struct result res;
    for (int i = 1; i <= m_judgenum; i++)
    {
        std::string index = std::to_string(i);
        std::string input_path = m_datapath + index + ".in";
        conf.input_path = input_path.data();
        std::string output_path = m_runpath + index + ".out";
        conf.output_path = output_path.data();

        run(&conf, &res);

        JudgmentResult(res, index);
    }
    return true;
}

bool Judger::JudgmentResult(struct result &res, std::string &index)
{
    Json::Value testinfo;
    //获取最大运行时间和最大内存
    m_runtime = std::max(m_runtime, res.cpu_time);
    m_runmemory = std::max(m_runmemory, res.memory);

    testinfo["RunTime"] = std::to_string(m_runtime) + "MS";
    testinfo["RunMemory"] = std::to_string(int(m_runmemory / 1024 / 1024)) + "MB";

    //获取输入、输出、正确答案
    //输入
    std::string inputpath = m_datapath + index + ".in";
    std::ifstream inputfile(inputpath.c_str());
    if (!inputfile.is_open())
    {
        m_result = SE;
        return false;
    }
    std::string standardinput{
        std::istreambuf_iterator<char>(inputfile),
        std::istreambuf_iterator<char>()
    };
    inputfile.close();

    //标准答案
    std::string outputpath = m_datapath + index + ".out";
    std::ifstream outputfile(outputpath.c_str());
    if (!outputfile.is_open())
    {
        m_result = SE;
        return false;
    }
    std::string standardanswer{
        std::istreambuf_iterator<char>(outputfile),
        std::istreambuf_iterator<char>()
    };
    outputfile.close();

    //输出
    std::string answerpath = m_runpath + index + ".out";
    std::ifstream answerfile(answerpath.c_str());
    if (!answerfile.is_open())
    {
        m_result = SE;
        return false;
    }
    std::string calculateanswer{
        std::istreambuf_iterator<char>(answerfile),
        std::istreambuf_iterator<char>()
    };
    answerfile.close();

    //去除结尾的空格
    if (!calculateanswer.empty() && calculateanswer.back() == '\n')
    {
        calculateanswer.pop_back();
    }

    testinfo["StandardInput"] = standardinput;
    testinfo["StandardOutput"] = standardanswer;
    testinfo["PersonalOutput"] = calculateanswer;

    //获取judge状态
    Status status;
    int result = res.result;
    if (result == 0) //正常返回
    {
        if (res.cpu_time > m_timelimit)
        {
            m_result = TLE;
            status = TLE;
        }
        else if (res.memory > m_memorylimit)
        {
            m_result = MLE;
            status = MLE;
        }
        else if (m_isspj) //spj判断
        {
            status = AC;
            m_command = m_datapath + "spj" + " " + inputpath + " " + outputpath + " " + answerpath;
            if (system(m_command.c_str()) != 0)
            {
                m_result = WA;
                status = WA;
            }
        }
        else //普通判断
        {
            status = AC;
            if (standardanswer != calculateanswer)
            {
                status = WA;
                m_result = WA;
            }
        }
    }
    else if (result == CPU_TIME_LIMIT_EXCEEDED)
    {
        m_result = TLE;
        status = TLE;
    }
    else if (result == REAL_TIME_LIMIT_EXCEEDED)
    {
        m_result = TLE;
        status = TLE;
    }
    else if (result == MEMORY_LIMIT_EXCEEDED)
    {
        m_result = MLE;
        status = MLE;
    }
    else if (result == RUNTIME_ERROR)
    {
        //获取错误信息
        std::string errorpath = m_runpath + "error.out";
        std::ifstream errorfile(errorpath.c_str());
        if (!errorfile.is_open())
        {
            m_result = SE;
            return false;
        }
        std::string errorstring{
            std::istreambuf_iterator<char>(errorfile),
            std::istreambuf_iterator<char>()
        };
        errorfile.close();

        m_reason = errorstring;
        m_result = RE;
        status = RE;
    }
    else if (result == SYSTEM_ERROR)
    {
        //获取日志
        std::string logpath = m_runpath + "judger.log";
        std::ifstream logfile(logpath.c_str());
        if (!logfile.is_open())
        {
            m_result = SE;
            return false;
        }
        char reason[100];
        logfile.getline(reason, 100);
        logfile.close();

        m_reason = reason;
        m_result = SE;
        status = SE;
    }

    testinfo["Status"] = status;
    m_resjson["TestInfo"].append(testinfo);
    return true;
}

bool Judger::CompileSPJ()
{
    //如果有SPJ可执行文件
    m_command = m_datapath + "spj";
    if (access(m_command.data(),F_OK) == 0)
    {
        m_isspj = true;
        return true;
    }

    //如果没有SPJ.cpp
    m_command = m_datapath + "spj.cpp";
    if (access(m_command.data(),F_OK) != 0)
    {
        return false;
    }

    //编译SPJ.cpp
    m_command = "timeout 10 g++ " + m_datapath + "spj.cpp -o " + m_datapath + "spj -O2 -std=c++14";
    if (system(m_command.c_str()) == -1)
    {
        m_result = SE;
        return false;
    }

    //如果有SPJ可执行文件
    m_command = m_datapath + "spj";
    if (access(m_command.data(),F_OK) == 0)
    {
        m_isspj = true;
        return true;
    }

    return true;
}
